package com.meta.platform.login.plus.wechat.service.impl;

import javax.annotation.Resource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.meta.act.app.service.ISysConfigService;
import com.meta.act.app.service.ISysUserService;
import com.meta.framework.act.entity.SysUser;
import com.meta.framework.act.model.LoginUser;
import com.meta.framework.common.constant.Constants;
import com.meta.framework.common.exception.BaseException;
import com.meta.framework.common.exception.CustomException;
import com.meta.framework.common.utils.MessageUtils;
import com.meta.framework.common.utils.SecurityUtils;
import com.meta.framework.common.utils.StringUtils;
import com.meta.framework.domain.event.DomainEventPulish;
import com.meta.framework.manager.AsyncManager;
import com.meta.framework.manager.factory.AsyncFactory;
import com.meta.framework.service.TokenService;
import com.meta.framework.util.UserUtil;
import com.meta.platform.login.plus.common.constants.WeChatConstants;
import com.meta.platform.login.plus.common.vo.LoginSuccessVo;
import com.meta.platform.login.plus.config.LoginPlusProperties;
import com.meta.platform.login.plus.security.LoginSimpleAuthenticationToken;
import com.meta.platform.login.plus.vcode.event.WeixinLoginUserEvent;
import com.meta.platform.login.plus.wechat.http.WeChatClient;
import com.meta.platform.login.plus.wechat.http.req.JsCode2SessionRequest;
import com.meta.platform.login.plus.wechat.http.resp.JsCode2SessionResponse;
import com.meta.platform.login.plus.wechat.proxy.MutipWeixinService;
import com.meta.platform.login.plus.wechat.proxy.WechatAppInfo;
import com.meta.platform.login.plus.wechat.rest.req.WeChatMinDecryptPhoneNumberCmd;
import com.meta.platform.login.plus.wechat.rest.req.WeichatAuthCmd;
import com.meta.platform.login.plus.wechat.rest.req.WeichatCodeLoginCmd;
import com.meta.platform.login.plus.wechat.rest.res.WechatUserInfoVo;
import com.meta.platform.login.plus.wechat.service.WeChatMinProgramService;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 微信小程序业务处理
 *
 * @author pengjianfeng
 * @version 1.0
 * @date 2021/1/21 5:16 下午
 */
@Slf4j
@Service
public class WeChatMinProgramServiceImpl implements WeChatMinProgramService {

    @Resource
    private WeChatClient weChatClient;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ISysUserService iSysUserService;

    @Resource
    private ISysConfigService iSysConfigService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private TokenService tokenService;

    @Resource
    private MutipWeixinService mutipWeixinService;

    @Resource
    private DomainEventPulish eventPulish;

    /**
     * 获取session key
     *
     * @param cmd
     * @return
     */
    @Override
    public JsCode2SessionResponse getSessionKey(WeichatAuthCmd cmd) {

        WechatAppInfo wechatInfo = mutipWeixinService.getWechatInfo(cmd.getAppId());
        JsCode2SessionRequest jsCode2SessionRequest = new JsCode2SessionRequest().setAppId(wechatInfo.getMiniAppId())
                .setAppSecret(wechatInfo.getMiniSecret())
                .setJsCode(cmd.getLoginCode());

        JsCode2SessionResponse jsCode2SessionResponse = weChatClient.jsCode2Session(jsCode2SessionRequest);

        Optional.ofNullable(jsCode2SessionResponse).ifPresent(response -> {
            this.redisTemplate.opsForValue().set(jsCode2SessionResponse.getOpenId(),
                    jsCode2SessionResponse.getSessionKey(),
                    WeChatConstants.WECHAT_SESSION_KEY_TIMEOUT,
                    TimeUnit.MILLISECONDS);
        });

        return jsCode2SessionResponse;
    }

    public WechatUserInfoVo getUserInfo(WeChatMinDecryptPhoneNumberCmd decryptPhoneNumberCmd) {
        String sessionKey = (String) this.redisTemplate.opsForValue().get(decryptPhoneNumberCmd.getOpenId());
        if (StringUtils.isBlank(sessionKey)) {
            throw new BaseException("session key已过期，请重新授权登录");
        }

        // 获取手机号码
        String encryptedData = encryptedData(sessionKey, decryptPhoneNumberCmd);
        return JSONObject.parseObject(encryptedData, WechatUserInfoVo.class);
    }

    @Transactional
    public LoginSuccessVo codeLogin(WeichatCodeLoginCmd cmd){
      /*  String sessionKey = (String) this.redisTemplate.opsForValue().get(cmd.getOpenId());
        if (StringUtils.isBlank(sessionKey)) {
            throw new BaseException("session key已过期，请重新授权登录");
        }*/

        String phoneNumber = mutipWeixinService.getPhoneNumber(cmd.getAppId(), cmd.getCode());
        return phoneLogin(phoneNumber);
    }

    /**
     * 微信小程序解密手机号码；解密后，不存在注册并登录，存在直接登录
     *
     * @param decryptPhoneNumberCmd
     * @param decryptPhoneNumberCmd 密文消息
     */
    @Transactional
    public LoginSuccessVo registerAndLogin(WeChatMinDecryptPhoneNumberCmd decryptPhoneNumberCmd) {
        String sessionKey = (String) this.redisTemplate.opsForValue().get(decryptPhoneNumberCmd.getOpenId());
        if (StringUtils.isBlank(sessionKey)) {
            throw new BaseException("session key已过期，请重新授权登录");
        }

        String phoneNumber = this.getPhoneNumber(sessionKey, decryptPhoneNumberCmd);

        return phoneLogin(phoneNumber);
    }

    private LoginSuccessVo phoneLogin(String phoneNumber) {

        if (StringUtils.isBlank(phoneNumber)) {
            throw new BaseException("微信小程序授权登录失败");
        }

        SysUser sysUser = this.iSysUserService.selectUserByUserName(phoneNumber);
        if(Objects.isNull(sysUser)){
            String password = this.iSysConfigService.selectConfigByKey("sys.user.initPassword");
            SysUser user = new SysUser();
            user.setNickName(UserUtil.defaultUserName(phoneNumber));
            user.setUserName(phoneNumber);
            user.setPhonenumber(phoneNumber);
            user.setPassword(SecurityUtils.encryptPassword(password));
            user.setTenantId(1L);

            this.iSysUserService.insertUser(user);
            sysUser = user;
        }

        // 注册成功后，直接登录
        Authentication authentication;
        try {
            authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(sysUser.getUserName(), "CUSTOM_LOGIN_SMS"));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception e) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_FAIL, e.getMessage()));
            throw new CustomException(e.getMessage());
        }

        AsyncManager.me()
                .execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();

        eventPulish.publish(WeixinLoginUserEvent.builder().sysUser(loginUser.getUser()).build());

        return new LoginSuccessVo().setToken(tokenService.createToken(loginUser)).setUser(loginUser);
    }

    /**
     * 解密用户数据，获取手机号码
     *
     * @param decryptPhoneNumberCmd
     * @return
     */
    private String getPhoneNumber(String sessionKey, WeChatMinDecryptPhoneNumberCmd decryptPhoneNumberCmd) {

        try {
            String result = encryptedData(sessionKey, decryptPhoneNumberCmd);
            if (result == null) return null;
            return JSONObject.parseObject(result).getString("purePhoneNumber");
        } catch (Exception e) {
            log.error("decrypt phoneNumber error", e);
            throw new BaseException("微信小程序授权登录失败");
        }
    }

    private static String encryptedData(String sessionKey, WeChatMinDecryptPhoneNumberCmd decryptPhoneNumberCmd){

        try{

            // 被加密的数据
            byte[] dataByte = Base64.decode(decryptPhoneNumberCmd.getEncryptedData());
            // 加密秘钥
            byte[] keyByte = Base64.decode(sessionKey);
            // 偏移量
            byte[] ivByte = Base64.decode(decryptPhoneNumberCmd.getIv());
            // 如果密钥不足16位，那么就补足. 这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null == resultByte || resultByte.length == 0) {
                return null;
            }
            String result = new String(resultByte, "UTF-8");
            log.info("weixin login result:{}", result);
            return result;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        System.out.println(StringUtils.isBlank(" "));
    }
}
